package Logic;

import java.util.Vector;

import System.DataManager;
import System.SystemLogger;

public class Engine {
	
	
	private SystemLogger systemLog;
	private DataBase db;
	
	public Engine(){
		//loading the system log
		systemLog = SystemLogger.getInstance();		
		systemLog.send("Engine started");		
		db = new DataBase();
		
		
		
		/*
		int u = 0;
		int r = 0;
		User u1 = users.get(u);
		Rank r1 = u1.getRanks().get(r);
		System.out.println("uid = " + r1.getUser().getID() + r1.getMovie().getID() + r1.getRank());
		r1.setRank(11);
		r1 = u1.getRanks().get(r);
		System.out.println("uid = " + r1.getUser().getID() + r1.getMovie().getID() + r1.getRank());
		movies.get(0).printRanks();
		*/
		
		/*int size = users.size();
		for(int i=0; i<size; i++){
			users.get(i).printRanks();
		}
		
		int randUser = (int)(Math.random() * users.size());
		User a = users.get(randUser);
		getRecomendatin(a);*/		
		
		
	}
	
	
	public double getIntersection(User a, User b){
		double sumNum = 0;
		double sumDea = 0;
		double sumDeb = 0;
		
		Vector<Rank> aRanks = a.getRanks();
		Vector<Rank> bRanks = b.getRanks();
		int aRanksNum = aRanks.size();
		int bRanksNum = bRanks.size();
		int ai = 0;
		int bi = 0;
		int aiID = aRanks.get(ai).getMovie().getID();
		int biID = bRanks.get(bi).getMovie().getID();
		Vector<Double> aRelatives = new Vector<Double>(1);
		Vector<Double> bRelatives = new Vector<Double>(1);
		while ((ai < aRanksNum) && (bi < bRanksNum)){
			if (aiID == biID){
				//System.out.println("two users saw movie " + aRanks.get(ai).getMovie().toString());
				//System.out.println(aRanks.get(ai).getRelativeRank() + " and " + bRanks.get(bi).getRelativeRank());
				aRelatives.add(aRanks.get(ai).getRelativeRank());
				bRelatives.add(bRanks.get(bi).getRelativeRank());
				ai++;
				bi++;
				if (ai < aRanksNum)
					aiID = aRanks.get(ai).getMovie().getID();
				if (bi < bRanksNum)
					biID = bRanks.get(bi).getMovie().getID();				
			}
			else if (aiID > biID){
				bi++;
				if (bi < bRanksNum)
					biID = bRanks.get(bi).getMovie().getID();
			}
			else if (aiID < biID){
				ai++;
				if (ai < aRanksNum)
					aiID = aRanks.get(ai).getMovie().getID();
			}			
		}
		int interNum = aRelatives.size();
		for (int i=0; i<interNum; i++){
			sumNum += aRelatives.get(i) * bRelatives.get(i);
			sumDea += aRelatives.get(i) * aRelatives.get(i);
			sumDeb += bRelatives.get(i) * bRelatives.get(i);
		}
		if ((sumDea == 0) || (sumDeb == 0))
			return 0;
		else
			return (sumNum / Math.sqrt(sumDea*sumDeb));

	}	
	
	
	public void getRecomendation(User a){
		int usersSize = db.getUsersSize();
		Vector<Double> weights = new Vector<Double>(usersSize);//weights[i] = the w(a,i)
		User b;		
		System.out.println("Intersection between " + a.getUsername() + " to:");
		for (int i=0; i<usersSize; i++){			
			if (i != a.getID()){
				b = db.getUser(i);
				weights.add(getIntersection(a, b));
				System.out.println(b.getUsername() + "\tis\t" + getIntersection(a, b));				
			}
			else{
				weights.add(0.0);
			}
		}
		
		int moviesSize = db.getMoviesSize();
		int ranksSize; //number of ranks for each movie
		Movie m; //tmp movie
		Vector<Rank> movieRanks; //the ranks for each movie
		Rank r; //tmp rank
		int uID; //the user id of the rank
		double paj; //P(a,j)		
		double k; //normalize factor
		double maxPaj = 0.0;
		Movie maxM = null;	
		
		for (int i=0; i<moviesSize; i++){ //for each movie calculate P(a,j)			
			m = db.getMovie(i);
			if (!a.isSaw(m)){ //calculate only if a didn't saw this movie
				System.out.println("Calculate paj for " + m.toString());
				movieRanks = m.getRanks();
				ranksSize = movieRanks.size();
				paj = 0;
				k = 0;
				for (int j=0; j<ranksSize; j++){
					r = movieRanks.get(j);
					uID = r.getUser().getID();
					k += weights.get(uID);
					System.out.println("w(a,i) for user " + r.getUser().getUsername() + " is " + weights.get(uID) + " and user relative ranke " + r.getRelativeRank());
					paj += (weights.get(uID) * (r.getRank() - r.getUser().getAverageRank()));
				}
				
				k = 1 / Math.abs(k);
				System.out.println("k = " + k);
				paj = k * paj;
				paj += a.getAverageRank() + paj;
				System.out.println("---- paj = " + paj);
				if (paj > maxPaj){
					maxPaj = paj;
					maxM = m;				
				}	
			}
		}
		
		System.out.println("recomended movie: " + maxM.toString());
	}
	
	public Movie addNewMovie(String[] vals){
		Movie tmp = null;
		try {
			tmp = new Movie(vals);
			db.getMovieVector().add(tmp);
			db.getDataManager().createNewMovieNode(tmp);
			this.systemLog.send("the movie "+ tmp.getName()+ " with ID "+tmp.getID()+" was added to DB");
		}
		catch (Exception ex){
			System.out.println("Can't create new movie");
			System.out.println(ex.getMessage());
			this.systemLog.send("failed to add new movie: \"" + vals[0]+ "\" to DB");
		}
		return tmp;
	}
	
	public User addNewUser(String[] vals){
		User tmp = null;
		try {
			tmp = new User(vals);
			db.getUserVector().add(tmp);
			db.getDataManager().createNewUserNode(tmp);
			this.systemLog.send("a new user "+ tmp.username +
								" with ID: "+tmp.getID()+" was added to DB");
		}
		catch (Exception ex){
			System.out.println("Can't create new user");
			System.out.println(ex.getMessage());
			this.systemLog.send("failed to add new user: \""+vals[0]+ "\" to DB");
		}
		return tmp;
	}
	
	public synchronized void removeUser(User user){
		// remove the ranks the user made for movies from the movies data.
		for (Rank r: user.getRanks()){
			Movie mov = r.getMovie();
			mov.getRanks().remove(r);
		}
		// remove the user from the user vector.
		this.systemLog.send("user with ID "+user.getID()+"removed from the DB");
		this.db.getUserVector().remove(user);		
	}
	
	public synchronized void removeMovie(Movie movie){
		for (Rank r: movie.getRanks()){
			User u = r.getUser();
			u.getRanks().remove(r);
		}
		this.systemLog.send("the movie: "+movie.getName()+" removed from the DB");
		this.db.getMovieVector().remove(movie);

	}
	
	public synchronized void editMovieDetails(Movie m,String [] vals) {
		try {
			m.setValues(vals);
			this.db.getDataManager().saveMoviesData();
			this.systemLog.send("the movie: "+m.getName()+" detais were updated to the DB");
		} catch (Exception e) {
			this.systemLog.send("failed to update the movie: "+m.getName()+" detais in the DB");
			e.printStackTrace();
		}		
	}
	
	public synchronized void editUserDetails(User u,String [] vals) {
		try {
			u.setValues(vals);
			this.db.getDataManager().saveUsersData();
			this.systemLog.send("the user: "+u.getUsername()+" detais were updated to the DB");
		} catch (Exception e) {
			this.systemLog.send("failed to update user: "+u.getUsername()+" detais in the DB");
			e.printStackTrace();
		}		
	}


	public synchronized SystemLogger getSystemLog() {
		return systemLog;
	}


	public synchronized DataBase getDb() {
		return db;
	}


	public void shutDown() {
		this.db.getDataManager().saveData();
		
	}
	
}
